﻿using System;
using System.IO.Ports;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;

namespace Commnications
{
    /// <summary>
    /// 通讯句柄
    /// </summary>
    public class EasyContext
    {
        #region Constructor begin
        /// <summary>
        /// 通讯类型私有构造
        /// </summary>
        /// <param name="channelType"></param>
        private EasyContext(ChannelType channelType)
        {
            type = channelType;
        }
        public EasyContext(EndPoint remoteEndPoint, ChannelType channelType) :this(channelType)
        {
            RemoteEndPoint = remoteEndPoint;
        }
        /// <summary>
        /// 创建tcp or udp server
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="channelType"></param>
        public EasyContext(Socket socket, ChannelType channelType)
        {
            this.Net = socket;
            type = channelType;
        }
        /// <summary>
        /// tcp or udp server constructor
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="isTcp"></param>
        public EasyContext(Socket socket, bool isTcp = true) : this(isTcp ? ChannelType.Tcp : ChannelType.Udp)
        {
            this.Net = socket;
            RemoteIp = socket.RemoteEndPoint?.ToString();
        }
        /// <summary>
        /// udp client constructor
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="host">remote ip</param>
        /// <param name="port">remote port</param>
        public EasyContext(Socket socket, string host, int port) : this(ChannelType.Udp)
        {
            RemoteEndPoint = EasyHelper.GetIPEndPoint(host, port);
            this.Net = socket;
            RemoteIp = socket.RemoteEndPoint?.ToString();
        }
        /// <summary>
        /// serial constructor
        /// </summary>
        /// <param name="com"></param>
        /// <param name="action"></param>
        public EasyContext(System.IO.Ports.SerialPort com, Action<EasyContext, ChannelType> action = null)
        {
            this.Serial = com;
            RemoteIp = com.PortName;
            if (action != null)
            {
                OnNotify = action;
                this.Serial.DataReceived += OnMessage;
                isNotifyed = true;
            }
        }
        #endregion Constructor end
        /// <summary>
        /// 使用ssl进行客户端认证
        /// </summary>
        /// <param name="targetHost"></param>
        /// <param name="clientCertificates"></param>
        /// <param name="enabledSslProtocols"></param>
        /// <param name="checkCertificateRevocation"></param>
        public virtual void AuthenticateAsClient(string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols,
            bool checkCertificateRevocation, RemoteCertificateValidationCallback validateServerCertificate)
        {
            if (UseSsl && Net != null && type == ChannelType.Tcp)
            {
                _networkStream = new NetworkStream(Net, true);
                _sslStream = new SslStream(
                    _networkStream,
                    false,
                    validateServerCertificate,
                    null
                );
                // 执行SSL握手
                _sslStream.AuthenticateAsClient(targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation);
            }
        }
        #region Member begin
        public EndPoint RemoteEndPoint;
        public EndPoint LocalEndPoint { get; private set; }
        public void SetLocalEndPoint(EndPoint endPoint)
        {
            LocalEndPoint = endPoint;
        }
        Action<EasyContext, ChannelType> OnNotify;
        bool isNotifyed = false;
        public Action<string, int> logger;
        /// <summary>
        /// 从连接监听数据并转发
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnMessage(object sender, SerialDataReceivedEventArgs e)
        {
            //SerialPort sp = (SerialPort)sender;
            //string indata = sp.ReadExisting();
            //var encoder = Encoding.ASCII;
            try
            {
                OnNotify(this, ChannelType.EasyContext);
            }
            catch (Exception ex)
            {
                if (logger != null)
                    logger($"OnMessage err:{ex}", (int)EasyCommBase.LogLevel.Error);
            }
        }
        ChannelType type { get; set; }
        public int Type { get => (int)type; }
        public Socket Net { get; private set; }
        NetworkStream _networkStream;
        SslStream _sslStream;
        public System.IO.Ports.SerialPort Serial { get; private set; }
        public string RemoteIp
        {
            get; private set;
        }
        #endregion Member end
        #region Public interface begin
        public bool IsConnected
        {
            get
            {
                switch (type)
                {
                    case ChannelType.Serial:
                        return Serial != null && Serial.IsOpen;
                    case ChannelType.Udp:
                        return Net != null;
                    case ChannelType.Tcp:
                    default:
                        return Net != null && Net.Connected;
                }
            }
        }
        public int Send(byte[] buffer, EndPoint endPoint = null)
        {
            switch (type)
            {
                case ChannelType.Serial:
                    {
                        Serial.Write(buffer, 0, buffer.Length);
                        return buffer.Length;
                    }
                case ChannelType.Udp:
                    return Net.SendTo(buffer, endPoint ?? RemoteEndPoint);
                case ChannelType.Tcp:
                default:
                    {
                        if (UseSsl)
                        {
                            _sslStream.Write(buffer, 0, buffer.Length);
                            return buffer.Length;
                        }
                        else
                            return Net.Send(buffer);
                    }
            }
        }
        public int Receive(byte[] buffer, int len)
        {
            switch (type)
            {
                case ChannelType.Serial:
                    {
                        return Serial.Read(buffer, 0, len);
                    }
                case ChannelType.Udp:
                    return Net.ReceiveFrom(buffer, len, SocketFlags.None, ref RemoteEndPoint);
                case ChannelType.Tcp:
                default:
                    {
                        if (UseSsl)
                        {
                            return _sslStream.Read(buffer, 0, len);
                        }
                        else
                            return Net.Receive(buffer, len, SocketFlags.None);
                    }
            }
        }
        public int Receive(byte[] buffer)
        {
            switch (type)
            {
                case ChannelType.Serial:
                    {
                        if (Serial.BytesToRead == 0)
                        {
                            Thread.Sleep(5);
                        }
                        if (Serial.BytesToRead > 0)
                            return Serial.Read(buffer, 0, Serial.BytesToRead);
                        else
                            return 0;
                    }
                case ChannelType.Udp:
                    return Net.ReceiveFrom(buffer, ref RemoteEndPoint);
                case ChannelType.Tcp:
                default:
                    {
                        if (UseSsl)
                        {
                            return _sslStream.Read(buffer, 0, buffer.Length);
                        }
                        else
                            return Net.Receive(buffer);
                    }
            }
        }
        public void Close()
        {
            if (type == ChannelType.Serial)
            {
                if (Serial != null)
                {
                    if (isNotifyed)
                    {
                        this.Serial.DataReceived -= OnMessage;
                        isNotifyed = false;
                    }
                    Serial.Close();
                }
            }
            else
            {
                _sslStream?.Close();
                _networkStream?.Close();
                if (Net != null)
                {
                    Net.Close();
                }
            }
        }
        public void Dispose()
        {
            _sslStream?.Dispose();
            _networkStream?.Dispose();
            if (Net != null)
            {
                Net.Dispose();
                Net = null;
            }
            else if (Serial != null)
            {
                Serial.Dispose();
                Serial = null;
            }
        }
        public void SetChannelNull()
        {
            if (type == ChannelType.Serial)
                Serial = null;
            else
            {
                _sslStream = null;
                _networkStream = null;
                Net = null;
            }
        }
        /// <summary>
        /// 丢弃缓冲区数据（支持串口&TCP）
        /// </summary>
        public void DiscardInBuffer()
        {
            if (type == ChannelType.Serial)
            {
                Serial.DiscardInBuffer();
            }
            else if (type == ChannelType.Tcp)
            {
                while (Net.Available > 0)
                {
                    byte[] buffer = new byte[Math.Min(Net.Available, Net.ReceiveBufferSize)];
                    // 读取并丢弃缓冲区数据
                    Net.Receive(buffer, buffer.Length, SocketFlags.None);
                }
            }
        }
        public void Listen(int backlog)
        {
            Net.Listen(backlog);
        }
        /// <summary>
        /// 获取可读字节数
        /// </summary>
        public int BytesToRead
        {
            get
            {
                switch (type)
                {
                    case ChannelType.Serial:
                        return Serial.BytesToRead;
                    case ChannelType.Tcp:
                    case ChannelType.Udp:
                        return Net.Available;
                }
                return 0;
            }
        }
        public int ReadTimeout
        {
            get
            {
                if (type == ChannelType.Serial)
                    return Serial.ReadTimeout;
                else
                    return Net.ReceiveTimeout;
            }
            set
            {
                if (type == ChannelType.Serial)
                    Serial.ReadTimeout = value;
                else
                    Net.ReceiveTimeout = value;
            }
        }
        public int WriteTimeout
        {
            get
            {
                if (type == ChannelType.Serial)
                    return Serial.WriteTimeout;
                else
                    return Net.SendTimeout;
            }
            set
            {
                if (type == ChannelType.Serial)
                    Serial.WriteTimeout = value;
                else
                    Net.SendTimeout = value;
            }
        }
        public bool NoDelay
        {
            get
            {
                if (type == ChannelType.Serial)
                    return false;
                else
                    return Net.NoDelay;
            }
            set
            {
                if (type == ChannelType.Serial)
                {
                    //do nothing
                }
                else
                    Net.NoDelay = value;
            }
        }
        #endregion Public interface end
        public int ReceiveBufferSize
        {
            get
            {
                if (type == ChannelType.Serial)
                    return Serial.ReadBufferSize;
                else
                    return Net.ReceiveBufferSize;
            }
        }
        /// <summary>
        /// 是否开启安全传输
        /// </summary>
        public bool UseSsl { get; set; }
    }
    public enum ChannelType
    {
        Serial, Tcp, Udp,
        EasyContext,//接收数据通知
    }
}
